package io.renren.common.eth.web3j;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.methods.response.NewAccountIdentifier;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.Response;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.utils.Convert;

import com.alibaba.fastjson.JSONObject;

import rx.Observable;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.ArrayList.*;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
 
//@Slf4j
public class EthUtil {

    private static Logger log = LoggerFactory.getLogger(EthUtil.class);

    private static int WEI = 18;


    public static void main(String[] args) throws IOException {
//        getClientVersion();
//        getAccounts();
//        getBalance("0xcf7a554f74c63d4a7e16ad43310b6d2a6877cab3");
        unlockAccount("0xcf7a554f74c63d4a7e16ad43310b6d2a6877cab3", "987654321");
    }

    /**
     * 客户端版本号
     */
    public static void getClientVersion() {
        Web3j web3j = EthClient.getWeb3jClient();
        Web3ClientVersion web3ClientVersion = null;
        try {
            web3ClientVersion = web3j.web3ClientVersion().send();
        } catch (IOException e) {
            log.error("exception==>:", e);
        }
        String clientVersion = web3ClientVersion.getWeb3ClientVersion();
        log.info("clientVersion==>{}", clientVersion);
    }

    /**
     * 列出所有账户
     */
    public static List<String> getAccounts() throws IOException {
        Web3j web3j = EthClient.getWeb3jClient();
        Request<?, EthAccounts> ethAccountsRequest = web3j.ethAccounts();
        EthAccounts response = ethAccountsRequest.send();
        if (!response.hasError()) {
            log.info("ethAccounts==>{}", response.getResult());
            return response.getResult();
        }

        logError(response);
        return null;
    }

    /**
     * 账户以太币余额
     */
    public static BigInteger getBalance(String accountAddress) throws Exception {
        Admin admin = EthClient.getAdminClient();
        Request<?, EthGetBalance> ethGetBalanceRequest = admin.ethGetBalance(accountAddress, DefaultBlockParameterName.LATEST);
        EthGetBalance response = ethGetBalanceRequest.sendAsync().get();
        if (!response.hasError()) {
            log.info("balance==>{}", response.getBalance());
            return response.getBalance();
        }

        logError(response);
        return BigInteger.ZERO;
    }

    /**查token余额
     * 参数被查询的钱包地址
     * @throws Exception
     */

    public static BigDecimal getTokenBalance(String accountAddress, String tokenAddress) throws Exception {
        String functionName = "balanceOf";
        List<Type> inputParams = Arrays.asList((Type)new Address(accountAddress));
        List<TypeReference<?>> outParams = new ArrayList<>(1);
        outParams.add((TypeReference) new TypeReference<Uint256>() {});
        //查询者地址  合约地址 方法名称 查询地址  输出格式
        List<Type> call = EthUtil.call(accountAddress, tokenAddress, functionName, inputParams,outParams);
        //log.info("call result==>{}", JsonUtils.objectToJson(call));
        //  BigInteger decimal = Numeric.decodeQuantity(call.get(0).getValue().toString());
        return Convert.fromWei(call.get(0).getValue().toString(), Convert.Unit.ETHER);

    }
    /**
     * 新建账户
     *
     * @param password
     * @return
     * @throws Exception
     */
    public static boolean newAccount(String password) throws Exception {
        Admin admin = EthClient.getAdminClient();
        NewAccountIdentifier response = admin.personalNewAccount(password).sendAsync().get();
        if (!response.hasError()) {
            log.info("account==>{}", response.getAccountId());
            return true;
        }

        logError(response);
        return false;
    }

    /**
     * 创建账户
     *
     * @param userPassword 用户密码
     * @return
     * @throws Exception
     */
    public static String blockchainAccountPath = "/data/apps/geth-alltools-linux-amd64-1.8.2-b8b9f7f4/chain/testchain/keystore/";
    public static String createAccounts(String userPassword) throws Exception {
        return WalletUtils.generateNewWalletFile(userPassword, new File(blockchainAccountPath), true);
    }

    /**
     * 远程创建账户
     *
     * @param userPassword 用户密码
     * @return
     * @throws Exception
     */
    public static String createAccount(String userPassword) throws Exception {
        Admin admin = EthClient.getAdminClient();
        NewAccountIdentifier newAccountIdentifier = admin.personalNewAccount(userPassword).send();
        return newAccountIdentifier.getAccountId();
    }

    /**
     * 账户解锁
     *
     * @param accountAddress
     * @param password
     * @throws IOException
     */
    public static boolean unlockAccount(String accountAddress, String password) throws IOException {
        Admin admin = EthClient.getAdminClient();
        Request<?, PersonalUnlockAccount> personalUnlockAccountRequest = admin.personalUnlockAccount(accountAddress, password);
        PersonalUnlockAccount response = personalUnlockAccountRequest.send();
        if (!response.hasError()) {
            log.info("result==>{}", response.accountUnlocked());
            return response.accountUnlocked();
        }

        logError(response);
        return false;
    }

    /**
     * 调用合约
     *
     * @param from
     * @param to
     * @param functionName
     * @param inputParams  入参
     * @return
     * @throws IOException
     */
    public static String call(String from, String to, String functionName,
                              List<Type> inputParams) throws IOException {
        Admin admin = EthClient.getAdminClient();
        //List<TypeReference<?>> outParams = Arrays.asList((TypeReference<?>)new TypeReference<Type>(){});
        List<TypeReference<?>> outParams = new ArrayList<TypeReference<?>>();
        Function function = new Function(functionName, inputParams, outParams);
        String encode = FunctionEncoder.encode(function);
        Request<?, EthCall> ethCallRequest = admin.ethCall(Transaction.createEthCallTransaction(from, to, encode), DefaultBlockParameterName.LATEST);
        EthCall response = ethCallRequest.send();
        if (!response.hasError()) {
        	System.out.println(JSONObject.toJSON(response));
            return response.getValue();
        }
        logError(response);
        return null;
    }

    /**
     * 调用合约
     *
     * @param from
     * @param to
     * @param functionName
     * @param inputParams  入参
     * @param outParams    出参
     * @return
     * @throws IOException
     */
    public static List<Type> call(String from, String to, String functionName,
                               List<Type> inputParams, List<TypeReference<?>> outParams) throws IOException {
        Admin admin = EthClient.getAdminClient();
        Function function = new Function(functionName, inputParams, outParams);
        String encode = FunctionEncoder.encode(function);
        Request<?, EthCall> ethCallRequest = admin.ethCall(Transaction.createEthCallTransaction(from, to, encode), DefaultBlockParameterName.LATEST);
        EthCall response = ethCallRequest.send();
        if (!response.hasError()) {
        	System.out.println(FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters()));
            return FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());
        }
        logError(response);
        return null;
    }

    /**
     * 以太币转账
     *
     * @param from
     * @param password
     * @param to
     * @param amount   unit ether
     * @return
     * @throws IOException
     * @throws CipherException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String transfer(String from, String password, String to, Integer amount) throws IOException, CipherException, ExecutionException, InterruptedException {
        Admin admin = EthClient.getAdminClient();
        if (unlockAccount(from, password)) {
            BigInteger value = toWei(amount);
            Request<?, EthSendTransaction> ethSendTransactionRequest = admin.ethSendTransaction(Transaction.createEtherTransaction(from, null, null, null, to, value));
            EthSendTransaction response = ethSendTransactionRequest.sendAsync().get();
            if (!response.hasError()) {
                log.info("TransactionHash==>{}", response.getTransactionHash());
                return response.getTransactionHash();
            }
            logError(response);
        }
        return null;
    }

    /**
     * 代币转账
     *
     * @param from
     * @param to
     * @param amount
     * @return String  transactionsHash
     * @throws Exception
     */
    public String tokenTransferFrom(String from, String to, String password, String tokenAddress, BigDecimal amount) throws Exception {
        BigInteger transferAmount = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
        log.info("transferAmount----->{}"+transferAmount);
        List<TypeReference<?>> outParams = new ArrayList<>(1);
        outParams.add((TypeReference)new TypeReference<Type>() {});
        String result = EthUtil.sendTransaction(from, tokenAddress, password, "transfer",
                Arrays.asList(new Address(to)), outParams);
        log.info("sendTransaction==>{}", result);
        return result;
    }

    /**
     * 发送事务，返回transactionHash
     *
     * @param from
     * @param to
     * @param password
     * @param functionName
     * @param inputParams
     * @param outParams
     * @return
     * @throws IOException
     */
    public static String sendTransaction(String from, String to, String password, String functionName,
                                         List<Type> inputParams, List<TypeReference<?>> outParams) throws IOException {
        Admin admin = EthClient.getAdminClient();
        Function function = new Function(functionName, inputParams, outParams);
        String encode = FunctionEncoder.encode(function);
        Request<?, EthSendTransaction> ethSendTransactionRequest = admin.personalSendTransaction(Transaction.createEthCallTransaction(from, to, encode), password);
      
       
        EthSendTransaction response = ethSendTransactionRequest.send();
       
        if (!response.hasError()) {
            log.info("TransactionHash==>{}", response.getTransactionHash());
            return response.getTransactionHash();
        }

        logError(response);
        return null;
    }


    /**
     * 根据交易hash获取交易结果
     *
     * @param transactionHash
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static Optional<TransactionReceipt> getTransactionReceipt(String transactionHash) throws ExecutionException, InterruptedException {
        Admin admin = EthClient.getAdminClient();
        Request<?, EthGetTransactionReceipt> ethGetTransactionReceiptRequest = admin.ethGetTransactionReceipt(transactionHash);
        EthGetTransactionReceipt response = ethGetTransactionReceiptRequest.sendAsync().get();
        if (!response.hasError()) {
            log.info("TransactionReceipt==>{}", response.getTransactionReceipt());
            return response.getTransactionReceipt();
        }

        logError(response);
        return null;
    }

    public static String getStorageAt(String address, int positionIndex) throws IOException {
        Admin admin = EthClient.getAdminClient();
        Request<?, EthGetStorageAt> ethGetStorageAtRequest = admin.ethGetStorageAt(address, BigInteger.valueOf(positionIndex), DefaultBlockParameterName.LATEST);
        EthGetStorageAt response = ethGetStorageAtRequest.send();
        return response.getData();
    }

    private static void logError(Response response) {
        Response.Error error = response.getError();
        log.info("response_error==>code:{},message:{}", error.getCode(), error.getMessage());
    }

    private static BigInteger toWei(Integer value) {
        return Convert.toWei(BigDecimal.valueOf(value), Convert.Unit.ETHER).toBigInteger();
    }

    public static void blockObservable() {
        Admin admin = EthClient.getAdminClient();
        Observable<EthBlock> blockObservable = admin.blockObservable(true);
    }

}
